/**
 * @file		dd_pmc_adc.c
 * @brief		PMC ADC driver
 * @note		None
 * @attention	None
 * 
 * <B><I>Copyright 2016 Socionext Inc.</I></B>
 */

#include <string.h>
#include "dd_pmc_adc.h"
#include "dd_pmc_tmr32.h"
#include "dd_pmc_hdmac.h"
#include "pmc.h"
#include "dd_arm.h"

/*----------------------------------------------------------------------*/
/* Definition															*/
/*----------------------------------------------------------------------*/
#define dd_pmc_adc_dsb() Dd_ARM_Dsb_Pou()

// DMA channel number : 0ch
#define D_DD_PMC_ADC_HDMAC_CH		(0)

/*----------------------------------------------------------------------*/
/* Enumeration															*/
/*----------------------------------------------------------------------*/
// Nothing Special

/*----------------------------------------------------------------------*/
/* Structure															*/
/*----------------------------------------------------------------------*/
// Nothing Special

/*----------------------------------------------------------------------*/
/* Global Data															*/
/*----------------------------------------------------------------------*/
// ADC_DATA Buffer
static volatile T_DD_PMC_ADC_DATA	gDD_PMC_ADC_Data;

// Callback function pointer
static volatile VP_CALLBACK			gDD_PMC_ADC_CallBack	= NULL;

// DMA Transfer
static volatile int					gDD_PMC_ADC_DMA_Flag		= 0;
static volatile ULONG				gDD_PMC_ADC_Sram_Address	= 0;

// Callback function pointer (DMA)
static volatile VP_CALLBACK			gDD_PMC_ADC_DMA_CallBack	= NULL;

/*----------------------------------------------------------------------*/
/* Macro																*/
/*----------------------------------------------------------------------*/
// Nothing Special

/*----------------------------------------------------------------------*/
/* Local Function														*/
/*----------------------------------------------------------------------*/
static VOID dd_pmc_adc_tmr32_callback( VOID );
static VOID dd_pmc_adc_hdmac_callback( UCHAR dma_ch, USHORT* const status );
static INT32 dd_pmc_adc_dma_transfer( VOID );

/**
 * @brief  32-bit Reload Timer callback function..
*/
static VOID dd_pmc_adc_tmr32_callback( VOID )
{
	// Reload timer restart.
	Dd_PMC_TMR32_StartReload();
}

/**
 * @brief  HDMAC callback function.
*/
static VOID dd_pmc_adc_hdmac_callback( UCHAR dma_ch, USHORT* const status )
{
	INT32	ret = D_DDIM_OK;
	
	if (dma_ch != D_DD_PMC_ADC_HDMAC_CH) {
		// ch unmatch.
		return;
	}
	
	if (*status != D_DD_PMC_HDMAC_SS_NORMAL_END) {
		// status error.
		return;
	}
	
	if( gDD_PMC_ADC_DMA_CallBack ){
		// Call Back function (retrun Next Destination Address)
		(*gDD_PMC_ADC_DMA_CallBack)(&gDD_PMC_ADC_Sram_Address);
	}
	
	// Set Destination Address.
	ret = Dd_PMC_HDMAC_Set_Dst_Addr( D_DD_PMC_ADC_HDMAC_CH, gDD_PMC_ADC_Sram_Address );
	if( ret != D_DDIM_OK ){
		Ddim_Print(("dd_pmc_adc_hdmac_callback() set dst_addr error. ret=0x%x\n", ret));
	}

	// Restart HDMAC transfer.
	ret = Dd_PMC_HDMAC_Start_Async( D_DD_PMC_ADC_HDMAC_CH, (VP_CALLBACK)dd_pmc_adc_hdmac_callback );
	if( ret != D_DDIM_OK ){
		Ddim_Print(("dd_pmc_adc_hdmac_callback() hdmac start error. ret=0x%x\n", ret));
	}
}

/**
 * @brief  Set DMA Transfer.
 * @return INT32 D_DDIM_OK/D_DD_PMC_ADC_INPUT_PARAM_ERR/D_DD_PMC_ADC_DMA_SS_ERROR
 */
static INT32 dd_pmc_adc_dma_transfer( VOID )
{
	INT32				ret;
	T_DD_PMC_HDMAC_CTRL	hdmac_ctrl;
	
	// Set DMA info. 
	hdmac_ctrl.config_a.bit.tc			= 0;								// Timer 1 cycles is transferred.
	hdmac_ctrl.config_a.bit.bc			= 7;								// Block count 8.
	hdmac_ctrl.config_a.bit.bt			= D_DD_PMC_HDMAC_BT_INCR;			// INCR.
	hdmac_ctrl.config_a.bit.is			= D_DD_PMC_HDMAC_IS_IDREQ_0;		// IDREQ[0]:ADC DMA request.
	
	hdmac_ctrl.config_b.bit.dp			= 0;								// Destination Protection.
	hdmac_ctrl.config_b.bit.sp			= 0;								// Source Protection.
	hdmac_ctrl.config_b.bit.ss			= D_DD_PMC_HDMAC_SS_NONE;			// Stop Status.
	hdmac_ctrl.config_b.bit.ci			= 1;								// Completion Interrupt.
	hdmac_ctrl.config_b.bit.ei			= 0;								// Error Interrupt.
	hdmac_ctrl.config_b.bit.rd			= D_DD_PMC_HDMAC_RD_DISABLE;		// Reload Destination.
	hdmac_ctrl.config_b.bit.rs			= D_DD_PMC_HDMAC_RS_DISABLE;		// Reload Source.
	hdmac_ctrl.config_b.bit.rc			= D_DD_PMC_HDMAC_RC_DISABLE;		// Reload Count.
	hdmac_ctrl.config_b.bit.fd			= D_DD_PMC_HDMAC_FD_INCR;			// Fixed Destination.
	hdmac_ctrl.config_b.bit.fs			= D_DD_PMC_HDMAC_FS_INCR;			// Fixed Source.
	hdmac_ctrl.config_b.bit.tw			= D_DD_PMC_HDMAC_TW_WORD;			// Transfer Width.
	hdmac_ctrl.config_b.bit.ms			= D_DD_PMC_HDMAC_MS_BLK;			// Mode Select.
	hdmac_ctrl.config_b.bit.tt			= 0;								// Transfer Type.
	
	hdmac_ctrl.src_addr					= (ULONG)(&(IO_PMC.ADC.ADD[0]));	// A/D Data Register address.
	hdmac_ctrl.dst_addr					= gDD_PMC_ADC_Sram_Address;			// SRAM address.
	
	ret = Dd_PMC_HDMAC_Ctrl_Common( D_DD_PMC_ADC_HDMAC_CH, &hdmac_ctrl );
	if( ret != D_DDIM_OK ){
		Ddim_Print(("Dd_PMC_HDMAC_Ctrl_Common() error. ret=0x%x\n", ret));
		return D_DD_PMC_ADC_INPUT_PARAM_ERR;
	}

	// Start HDMAC transfer.
	ret = Dd_PMC_HDMAC_Start_Async( D_DD_PMC_ADC_HDMAC_CH, (VP_CALLBACK)dd_pmc_adc_hdmac_callback );
	if( ret != D_DDIM_OK ){
		Ddim_Print(("Dd_PMC_HDMAC_Start_Async() error. ret=0x%x\n", ret));
		return D_DD_PMC_ADC_INPUT_PARAM_ERR;
	}
	
	return D_DDIM_OK;
}

/*----------------------------------------------------------------------*/
/* Grobal Function														*/
/*----------------------------------------------------------------------*/
/**
 * @brief  The clock supply of ADC begins. Moreover, the sampling variable is initialized.
 * @return VOID
 */
VOID Dd_PMC_ADC_Init( VOID )
{
	IO_PMC.ADC.ADC1.word	= 0x00001000;	// Reset(ADRST:1)
	
	IO_PMC.ADC.ADC1.word	= 0x00000000;	// Reset cancel(ADRST:0), XPDCTL:Auto power on
	IO_PMC.ADC.ADC2.word	= 0x00000000;

	IO_PMC.ADC.ADMR.word	= 0x00001CBB;	// Interruption permission

	IO_PMC.ADC.ADST.word	= 0x00000000;
	IO_PMC.ADC.ADCRL.word	= 0x00000000;
	IO_PMC.ADC.ADCRH.word	= 0x000003FF;

	// Global data clear.
	gDD_PMC_ADC_CallBack		= NULL;
	gDD_PMC_ADC_DMA_CallBack	= NULL;
	gDD_PMC_ADC_DMA_Flag		= 0;
	gDD_PMC_ADC_Sram_Address	= 0;
	memset( (void*)&gDD_PMC_ADC_Data, 0, sizeof(gDD_PMC_ADC_Data) );
}

/**
 * @brief  The ADC control condition is set.
 * @param  T_DD_PMC_ADC_CTRL* adc_ctrl
 * @return INT32 D_DDIM_OK/D_DD_PMC_ADC_INPUT_PARAM_ERR
 */
INT32 Dd_PMC_ADC_Ctrl( T_DD_PMC_ADC_CTRL const* const adc_ctrl )
{
	INT32 result;
	INT32 adc_num = D_DD_PMC_ADC_NUM;

#ifdef CO_PARAM_CHECK
	if( adc_ctrl == NULL ){
		Ddim_Assertion(("Dd_PMC_ADC_Ctrl: input param error. adc_ctrl is NULL.\n"));
		return D_DD_PMC_ADC_INPUT_PARAM_ERR;
	}
#endif

	// channel number range check
	if( (adc_ctrl->ch >= (1 << adc_num)) || (adc_ctrl->ch == 0) ){
		result = D_DD_PMC_ADC_INPUT_PARAM_ERR;
	}
	else{
		if( IO_PMC.ADC.ADST.bit.BUSY == 0 ){
			IO_PMC.ADC.ADC1.word	&= (~((1 << adc_num) - 1));					// ADCS mask
			IO_PMC.ADC.ADC1.word	|= adc_ctrl->ch;							// Channel number	@ADC1:ADCS

			IO_PMC.ADC.ADMR.word	&= 0x0000FCFF;								// MD mask
			IO_PMC.ADC.ADMR.word	|= ((adc_ctrl->cnv_mode & 0x3) << 8);		// Conversion mode	@ADMR;MD
			IO_PMC.ADC.ADMR.word	|= adc_ctrl->sampling_time;					// Sampling time	@ADMR:STS
		}

		IO_PMC.ADC.ADC1.word		&= 0x000093FF;								// XPDCTL, TGS, TSEL mask
		if( (adc_ctrl->start_trig == E_DD_PMC_ADC_START_TRIG_TIMER) ||
		   (adc_ctrl->start_trig == E_DD_PMC_ADC_START_TRIG_BOTH) ){
			IO_PMC.ADC.ADC1.word	|= ((adc_ctrl->timer_trig & 0x3) << 10);	// Timer trigger	@ADC1:TGS
			IO_PMC.ADC.ADC1.word	|= ((adc_ctrl->tsel & 0x1) << 13);			// TSEL				@ADC1:TSEL
		}
		IO_PMC.ADC.ADC1.word		|= ((adc_ctrl->powerdown & 0x1) << 14);		// Auto power down	@ADC1:XPDCTL
//		IO_PMC.ADC.ADC1.word		|= 0x00000200;								// Power ON			@ADC1:XPD

		IO_PMC.ADC.ADC2.word		&= 0x00000FFF;								// ETEDG, STG mask
		IO_PMC.ADC.ADC2.word		|= ((adc_ctrl->ext_edge & 0x1) << 15);		// EXT trigger edge	@ADC2:ETEDG
		IO_PMC.ADC.ADC2.word		|= ((adc_ctrl->start_trig & 0x7) << 12);	// Start trigger	@ADC2:STG

		switch( adc_ctrl->cnv_mode ){
			case E_DD_PMC_ADC_CONV_MODE_CONT_LOW:
				// convert continuously until low level
				IO_PMC.ADC.ADCRL.word = adc_ctrl->cmp_data;		// AD comparison register
				break;

			case E_DD_PMC_ADC_CONV_MODE_CONT_HIGH:
				// convert continuously until high level
				IO_PMC.ADC.ADCRH.word = adc_ctrl->cmp_data;		// AD comparison register
				break;

			default:
				break;
		}

		result = D_DDIM_OK;
	}
	return result;
}

/**
 * @brief  The ADC control condition is get.
 * @param  T_DD_PMC_ADC_CTRL* adc_ctrl
 * @return INT32 D_DDIM_OK/D_DD_PMC_ADC_INPUT_PARAM_ERR
 */
INT32 Dd_PMC_ADC_Get_Ctrl( T_DD_PMC_ADC_CTRL* const adc_ctrl )
{
	INT32 result;

#ifdef CO_PARAM_CHECK
	if( adc_ctrl == NULL ){
		Ddim_Assertion(("Dd_PMC_ADC_Get_Ctrl: input param error. adc_ctrl is NULL.\n"));
		return D_DD_PMC_ADC_INPUT_PARAM_ERR;
	}
#endif

	adc_ctrl->ch			= (IO_PMC.ADC.ADC1.word & 0x000000FF);								// Channel number	@ADC1:ADCS

	adc_ctrl->cnv_mode		= (E_DD_PMC_ADC_CONV_MODE)((IO_PMC.ADC.ADMR.word >> 8) & 0x3);		// Conversion mode	@ADMR:MD
	adc_ctrl->sampling_time = (IO_PMC.ADC.ADMR.word & 0x000000FF);								// Conversion mode	@ADMR:STS
	adc_ctrl->timer_trig	= (E_DD_PMC_ADC_TIMER_TRIG)((IO_PMC.ADC.ADC1.word >> 10) & 0x3);	// Timer trigger	@ADC1:TGS
	adc_ctrl->tsel			= (E_DD_PMC_ADC_TSEL)((IO_PMC.ADC.ADC1.word >> 7) & 0x1);			// TSEL				@ADC1:TSEL
	adc_ctrl->powerdown		= ((IO_PMC.ADC.ADC1.word >> 14) & 0x1);								// Auto power down	@ADC1:XPDCTL
	adc_ctrl->ext_edge		= (E_DD_PMC_ADC_EXT_TRIG_EDGE)((IO_PMC.ADC.ADC2.word >> 15) & 0x1);	// EXT trigger edge	@ADC2:ETEDG
	adc_ctrl->start_trig	= (E_DD_PMC_ADC_START_TRIG)((IO_PMC.ADC.ADC2.word >> 12) & 0x7);	// Start trigger	@ADC2:STG

	switch( adc_ctrl->cnv_mode ){
		case E_DD_PMC_ADC_CONV_MODE_CONT_LOW:
			// convert continuously until low level
			adc_ctrl->cmp_data = IO_PMC.ADC.ADCRL.word;		// AD comparison register
			break;

		case E_DD_PMC_ADC_CONV_MODE_CONT_HIGH :
			// convert continuously until high level
			adc_ctrl->cmp_data = IO_PMC.ADC.ADCRH.word;		// AD comparison register
			break;

		default:
			break;
	}

	result = D_DDIM_OK;

	return result;
}

/**
 * @brief  Set Timer.
 * @param  ULONG conv_cycle
 * @return INT32 D_DDIM_OK/D_DD_PMC_ADC_INPUT_PARAM_ERR
 */
INT32 Dd_PMC_ADC_Set_Timer( ULONG conv_cycle )
{
	INT32 result;
#ifdef CO_PARAM_CHECK
	if( conv_cycle == 0 ){
		Ddim_Assertion(("Dd_PMC_ADC_Set_Timer: input param error. conv_cycle is 0.\n"));
		return D_DD_PMC_ADC_INPUT_PARAM_ERR;
	}
#endif
	
	// Set Timer
	result = Dd_PMC_TMR32_SetTimer(conv_cycle, dd_pmc_adc_tmr32_callback);
	if( result != D_DDIM_OK ){
		Ddim_Print(("Dd_PMC_ADC_Set_Timer() error. result=0x%x\n", result));
		return D_DD_PMC_ADC_INPUT_PARAM_ERR;
	}
	
	return D_DDIM_OK;
}

/**
 * @brief  Set DMA Transfer.
 * @param  ULONG sram_address
 * @return INT32 D_DDIM_OK/D_DD_PMC_ADC_INPUT_PARAM_ERR
 */
INT32 Dd_PMC_ADC_Set_DMA_Transfer( ULONG sram_address )
{
	INT32 result;
#ifdef CO_PARAM_CHECK
	if( sram_address == 0 ){
		Ddim_Assertion(("Dd_PMC_ADC_Set_DMA_Transfer: input param error. sram_address is 0.\n"));
		return D_DD_PMC_ADC_INPUT_PARAM_ERR;
	}
#endif
	
	// Set 
	gDD_PMC_ADC_Sram_Address = sram_address;
	
	// Set DMA Transfer.
	result = dd_pmc_adc_dma_transfer();
	if( result != D_DDIM_OK ){
		Ddim_Print(("Dd_PMC_ADC_Set_DMA_Transfer() error. result=0x%x\n", result));
		return result;
	}
	
	// Flag On.
	gDD_PMC_ADC_DMA_Flag	 = 1;
	
	return D_DDIM_OK;
}

/**
 * @brief  The analog to digital translation is started with a soft trigger.
 * @param  VP_CALLBACK callback
 * @return VOID
 */
VOID Dd_PMC_ADC_Start( VP_CALLBACK callback )
{
	// set Callback function pointer.
	if (gDD_PMC_ADC_DMA_Flag) {
		gDD_PMC_ADC_DMA_CallBack = callback;
	}
	else {
		gDD_PMC_ADC_CallBack = callback;
	}
	
	// Start Trigger Selection.
	UCHAR start_trigger = IO_PMC.ADC.ADC2.bit.STG;
	
	// ADC power ON.
	IO_PMC.ADC.ADC1.bit.XPD = 1;
	
	// Reset
	IO_PMC.ADC.ADC1.bit.ADRST = 1;
	// Reset release
	IO_PMC.ADC.ADC1.bit.ADRST = 0;
	
	// wait : 5ms.
	DDIM_User_Dly_Tsk( 5 );
	
	// Timer
	if ((start_trigger == E_DD_PMC_ADC_START_TRIG_TIMER) ||
		(start_trigger == E_DD_PMC_ADC_START_TRIG_BOTH)) {
		// Timer start.
		Dd_PMC_TMR32_StartReload();
	}
	
	// Soft trigger.
	if ((start_trigger == E_DD_PMC_ADC_START_TRIG_SOFT) ||
		(start_trigger == E_DD_PMC_ADC_START_TRIG_BOTH)) {
		// Trigger ON.
		IO_PMC.ADC.ADC1.bit.STRG = 1;
	}
	dd_pmc_adc_dsb();
}


/**
 * @brief  The analog to digital translation is stopped.
 * @return VOID
 */
VOID Dd_PMC_ADC_Stop( VOID )
{
	// Timer stop.
	Dd_PMC_TMR32_Stop();
	
	IO_PMC.ADC.ADC1.bit.STRG = 0;	// Soft trigger stop
	IO_PMC.ADC.ADC1.bit.XPD  = 0;	// ADC power down
	dd_pmc_adc_dsb();
}

/**
 * @brief  The clock supply to ADC is stopped.
 * @return VOID
 */
VOID Dd_PMC_ADC_Terminate( VOID )
{
	// Timer stop.
	Dd_PMC_TMR32_Stop();
	// DMA stop.
	Dd_PMC_HDMAC_Stop( D_DD_PMC_ADC_HDMAC_CH );
	// Flag Off.
	gDD_PMC_ADC_DMA_Flag	= 0;
	
	IO_PMC.ADC.ADC1.word = 0x00000000;	// XPDCTL:0(Auto power on)
	IO_PMC.ADC.ADC2.word = 0x00002000;	// STG:10(Soft trigger)
	IO_PMC.ADC.ADMR.word = 0x00000000;	// Interruption permission
	IO_PMC.ADC.ADST.word &= 0x0000FFF1;	// Interrupt clear
	dd_pmc_adc_dsb();
}


/**
 * @brief  The analog to digital translation value sampled in the analog to digital translation regardless of is returned.
 * @param  T_DD_PMC_ADC_DATA* adc
 * @return VOID
 */
VOID Dd_PMC_ADC_Get_Value( T_DD_PMC_ADC_DATA* const adc_data )
{
	INT32 adc_cnt;

#ifdef CO_PARAM_CHECK
	if( adc_data == NULL ){
		Ddim_Assertion(("Dd_PMC_ADC_Get_Value: input param error. adc_data is NULL.\n"));
		return;
	}
#endif

	if( (IO_PMC.ADC.ADMR.word & 0x00000300) == 0x00000100 ){
		for( adc_cnt = 0; adc_cnt < D_DD_PMC_ADC_NUM; adc_cnt++ ){
			gDD_PMC_ADC_Data.data[adc_cnt] = (USHORT)IO_PMC.ADC.ADD[adc_cnt].word;
		}
	}

	*adc_data = gDD_PMC_ADC_Data;
}


/**
 * @brief  The analog to digital translation value sampled in the analog to digital translation regardless of is returned.
 * @param[in]	ch			channel number
 * @param[out]	adc_data	ADC data
 * @return VOID
 */
VOID Dd_PMC_ADC_Get_Value_By_Ch( UCHAR ch, USHORT* const adc_data )
{
#ifdef CO_PARAM_CHECK
	if( ch >= D_DD_PMC_ADC_NUM ){
		Ddim_Assertion(("Dd_PMC_ADC_Get_Value_By_Ch: input param error. channel number = %d.\n", ch));
		return;
	}
	if( adc_data == NULL ){
		Ddim_Assertion(("Dd_PMC_ADC_Get_Value_By_Ch: input param error. adc_data is NULL.\n"));
		return;
	}
#endif

	if( (IO_PMC.ADC.ADMR.word & 0x00000300) == 0x00000100 ){
		gDD_PMC_ADC_Data.data[ch] = (USHORT)IO_PMC.ADC.ADD[ch].word;
	}
	*adc_data = gDD_PMC_ADC_Data.data[ch];
}



/**
 * @brief  The interruption flag is cleared, and the CallBack function is called.
 * @return VOID
 */
VOID Dd_PMC_ADC_Int_Handler( VOID )
{
	INT32 adc_cnt;

	if( (IO_PMC.ADC.ADST.word & 0x0000000e) == 0 ){
		return;
	}

	// ADC interrupt process
	IO_PMC.ADC.ADST.word &= 0x0000FFF1;	// Interrupt clear
//	IO_PMC.ADC.ADC1.word |= 0x00000200;	// Power on

	dd_pmc_adc_dsb();

	for( adc_cnt = 0; adc_cnt < D_DD_PMC_ADC_NUM; adc_cnt++ ){
		gDD_PMC_ADC_Data.data[adc_cnt] = (USHORT)IO_PMC.ADC.ADD[adc_cnt].word;
	}

	if((gDD_PMC_ADC_CallBack) && (!gDD_PMC_ADC_DMA_Flag)){
		(*gDD_PMC_ADC_CallBack)();	// Call Back function
	}
}

